在Java程式設計中,檔案操作是一個常見且重要的任務,無論是讀取配置檔案、寫入日誌、處理使用者上傳的檔案,還是管理應用程式的資料存儲,我們都需要進行檔案操作。
Java提供豐富的API來處理檔案,從傳統的java.io包到更現代的java.nio包,為開發者提供多種選擇。
Java提供多種方式來進行檔案操作,主要分為兩大類:傳統的I/O(java.io包)和新I/O(java.nio包,又稱NIO)。
傳統I/O主要使用以下類別進行檔案操作:
這些類別提供基本的檔案操作功能,如創建、刪除、重命名檔案,以及讀寫檔案內容。
Java NIO(New I/O)在Java 1.4中引入,提供更高效的I/O操作。NIO主要使用以下類別:
Java 7引入NIO.2,進一步增強檔案操作能力:
NIO.2使得檔案操作更加便捷和高效,同時保持與舊版API的兼容性。
File類別是Java I/O中最基本的類別之一,用於表示檔案系統中的檔案和目錄。雖然在Java 7之後,我們有更現代的Path和Files API,但是File類別仍然被廣泛使用,尤其是在處理舊版程式碼時。
創建File物件非常簡單,您可以使用檔案的路徑字串來初始化:
File file = new File("/path/to/file.txt");
File directory = new File("/path/to/directory");
File類別提供許多方法來操作檔案和目錄:
boolean created = file.createNewFile(); // 創建新檔案
boolean dirCreated = directory.mkdir(); // 創建目錄
boolean dirsCreated = directory.mkdirs(); // 創建多層目錄
boolean exists = file.exists();
boolean deleted = file.delete();
boolean renamed = file.renameTo(new File("/new/path/newname.txt"));
boolean isFile = file.isFile();
boolean isDirectory = file.isDirectory();
File類別還提供許多方法來獲取檔案的資訊:
long length = file.length(); // 獲取檔案大小(位元組)
long lastModified = file.lastModified(); // 獲取最後修改時間
String name = file.getName(); // 獲取檔案名稱
String path = file.getPath(); // 獲取檔案路徑
String absolutePath = file.getAbsolutePath(); // 獲取絕對路徑
您可以使用File類別來列出目錄中的檔案和子目錄:
File[] files = directory.listFiles(); // 獲取目錄中的所有檔案和子目錄
String[] fileNames = directory.list(); // 獲取目錄中的所有檔案和子目錄名稱
在Java中,檔案的讀寫操作是最常見的I/O任務之一。Java提供多種方式來進行檔案的讀寫,包括使用傳統的I/O類別和更現代的NIO.2 API。
這些類別用於讀寫位元資料:
// 讀取檔案
try (FileInputStream fis = new FileInputStream("input.txt")) {
int content;
while ((content = fis.read()) != -1) {
// 處理讀取的位元資料
System.out.print((char) content);
}
} catch (IOException e) {
e.printStackTrace();
}
// 寫入檔案
try (FileOutputStream fos = new FileOutputStream("output.txt")) {
String data = "Hello, World!";
fos.write(data.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
這些類別用於讀寫字元資料:
// 讀取檔案
try (FileReader fr = new FileReader("input.txt")) {
int content;
while ((content = fr.read()) != -1) {
System.out.print((char) content);
}
} catch (IOException e) {
e.printStackTrace();
}
// 寫入檔案
try (FileWriter fw = new FileWriter("output.txt")) {
fw.write("你好,世界!");
} catch (IOException e) {
e.printStackTrace();
}
這些類別提供緩衝功能,可以提高讀寫效能:
// 讀取檔案
try (BufferedReader br = new BufferedReader(new FileReader("input.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
// 寫入檔案
try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
bw.write("這是第一行");
bw.newLine();
bw.write("這是第二行");
} catch (IOException e) {
e.printStackTrace();
}
Java 7引入的NIO.2提供更簡潔的檔案讀寫方法:
import java.nio.file.*;
// 讀取檔案
try {
List<String> lines = Files.readAllLines(Paths.get("input.txt"));
for (String line : lines) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
// 寫入檔案
try {
List<String> lines = Arrays.asList("第一行", "第二行", "第三行");
Files.write(Paths.get("output.txt"), lines);
} catch (IOException e) {
e.printStackTrace();
}
對於大檔案,建議使用緩衝串流或NIO的Channel和Buffer:
// 使用FileChannel讀取大檔案
try (FileChannel channel = FileChannel.open(Paths.get("bigfile.dat"), StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (channel.read(buffer) != -1) {
buffer.flip();
// 處理緩衝區中的資料
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
在Java中,目錄操作是檔案系統管理的重要部分。無論是創建新目錄、列出目錄內容,還是遍歷目錄樹,Java都提供豐富的API來處理這些任務。
File dir = new File("newDirectory");
boolean created = dir.mkdir(); // 創建單層目錄
boolean createdMultiple = dir.mkdirs(); // 創建多層目錄
File dir = new File("myDirectory");
String[] fileList = dir.list(); // 獲取檔案名稱列表
File[] files = dir.listFiles(); // 獲取File物件列表
boolean deleted = dir.delete(); // 只能刪除空目錄
Path path = Paths.get("newDirectory");
try {
Files.createDirectory(path);
// 或者創建多層目錄
Files.createDirectories(path);
} catch (IOException e) {
e.printStackTrace();
}
try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get("myDirectory"))) {
for (Path file: stream) {
System.out.println(file.getFileName());
}
} catch (IOException e) {
e.printStackTrace();
}
Path start = Paths.get("rootDirectory");
try {
Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
System.out.println(file.toString());
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
e.printStackTrace();
}
Path path = Paths.get("directoryToDelete");
try {
Files.delete(path); // 只能刪除空目錄
// 或者使用遞迴刪除非空目錄
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
e.printStackTrace();
}
Java NIO.2提供監視目錄變化的功能:
Path dir = Paths.get("watchedDirectory");
try (WatchService watchService = FileSystems.getDefault().newWatchService()) {
dir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY);
while (true) {
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
System.out.println("Event kind: " + event.kind() + ". File affected: " + event.context() + ".");
}
key.reset();
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
檔案屬性包括檔案的大小、創建時間、最後修改時間、權限等資訊。Java提供多種方式來獲取和修改這些屬性。
File類別提供一些基本的方法來獲取檔案屬性:
File file = new File("example.txt");
// 獲取檔案大小(位元組)
long size = file.length();
// 獲取最後修改時間
long lastModified = file.lastModified();
// 檢查檔案權限
boolean canRead = file.canRead();
boolean canWrite = file.canWrite();
boolean canExecute = file.canExecute();
// 修改檔案權限
boolean setReadable = file.setReadable(true);
boolean setWritable = file.setWritable(true);
boolean setExecutable = file.setExecutable(true);
NIO.2提供更豐富的檔案屬性操作方法:
Path path = Paths.get("example.txt");
// 獲取基本屬性
BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class);
System.out.println("創建時間: " + attr.creationTime());
System.out.println("最後訪問時間: " + attr.lastAccessTime());
System.out.println("最後修改時間: " + attr.lastModifiedTime());
System.out.println("檔案大小: " + attr.size());
// 修改檔案時間屬性
FileTime newLastModifiedTime = FileTime.fromMillis(System.currentTimeMillis());
Files.setLastModifiedTime(path, newLastModifiedTime);
// 獲取和設置POSIX檔案權限(僅在支援POSIX的系統上)
try {
Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path);
permissions.add(PosixFilePermission.OWNER_EXECUTE);
Files.setPosixFilePermissions(path, permissions);
} catch (UnsupportedOperationException e) {
System.out.println("This file system does not support POSIX file permissions");
}
// 獲取檔案擁有者
UserPrincipal owner = Files.getOwner(path);
System.out.println("檔案擁有者: " + owner.getName());
// 設置檔案擁有者(需要適當的系統權限)
UserPrincipalLookupService lookupService = FileSystems.getDefault().getUserPrincipalLookupService();
UserPrincipal newOwner = lookupService.lookupPrincipalByName("newowner");
Files.setOwner(path, newOwner);
Path path = Paths.get("example.txt");
FileStore store = Files.getFileStore(path);
System.out.println("檔案系統: " + store.name());
System.out.println("總空間: " + store.getTotalSpace());
System.out.println("可用空間: " + store.getUsableSpace());
System.out.println("未分配空間: " + store.getUnallocatedSpace());
NIO.2引入檔案屬性視圖的概念,允許訪問特定檔案系統的屬性:
Path path = Paths.get("example.txt");
// 獲取DOS屬性視圖(僅在支援DOS屬性的檔案系統上可用)
try {
DosFileAttributes dosAttr = Files.readAttributes(path, DosFileAttributes.class);
System.out.println("是否為隱藏檔案: " + dosAttr.isHidden());
System.out.println("是否為系統檔案: " + dosAttr.isSystem());
} catch (UnsupportedOperationException e) {
System.out.println("This file system does not support DOS attributes");
}
Java 7引入的NIO.2(New I/O 2)改進Java的檔案操作能力。NIO.2提供更簡潔、更強大的API,使得檔案和目錄操作變得更加容易和高效。
Path是NIO.2中的核心概念,代表檔案系統中的路徑:
Path path = Paths.get("example.txt");
Path absolutePath = path.toAbsolutePath();
Path parentPath = path.getParent();
Path fileName = path.getFileName();
Files類別提供大量靜態方法來操作檔案和目錄:
// 創建檔案
Files.createFile(Paths.get("newfile.txt"));
// 複製檔案
Files.copy(Paths.get("source.txt"), Paths.get("destination.txt"));
// 移動檔案
Files.move(Paths.get("oldname.txt"), Paths.get("newname.txt"));
// 刪除檔案
Files.delete(Paths.get("tobedeleted.txt"));
// 讀取所有行
List<String> lines = Files.readAllLines(Paths.get("file.txt"));
// 寫入所有行
List<String> linesToWrite = Arrays.asList("Line 1", "Line 2", "Line 3");
Files.write(Paths.get("output.txt"), linesToWrite);
// 使用BufferedReader讀取大檔案
try (BufferedReader reader = Files.newBufferedReader(Paths.get("largefile.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
// 處理每一行
}
}
// 創建目錄
Files.createDirectory(Paths.get("newdir"));
// 創建多層目錄
Files.createDirectories(Paths.get("path/to/newdir"));
// 列出目錄內容
try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get("mydir"))) {
for (Path entry: stream) {
System.out.println(entry.getFileName());
}
}
NIO.2提供FileSystem類別來操作檔案系統:
// 獲取默認檔案系統
FileSystem fs = FileSystems.getDefault();
// 獲取所有根目錄
for (Path root : fs.getRootDirectories()) {
System.out.println(root);
}
// 獲取檔案存儲
for (FileStore store : fs.getFileStores()) {
System.out.println(store);
}
NIO.2引入WatchService,用於監視目錄變化:
WatchService watchService = FileSystems.getDefault().newWatchService();
Path path = Paths.get("watchedDir");
path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY);
while (true) {
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
System.out.println("Event kind: " + event.kind() + ". File affected: " + event.context() + ".");
}
key.reset();
}
NIO.2提供SeekableByteChannel介面,允許在檔案中隨機訪問:
try (SeekableByteChannel channel = Files.newByteChannel(Paths.get("file.txt"))) {
ByteBuffer buffer = ByteBuffer.allocate(10);
channel.position(100); // 移動到檔案的第100個位元組
channel.read(buffer);
buffer.flip();
// 處理讀取的資料
}
NIO.2簡化Java中的檔案操作,提供更現代、更強大的API。
對於新的專案,建議優先考慮使用NIO.2來進行檔案和目錄操作。
在進行Java檔案操作時,遵循一些最佳實踐可以幫助您寫出更安全、更高效的程式碼。以下是一些重要的建議和注意事項:
始終使用try-with-resources語句來自動關閉資源,這可以防止資源洩漏:
try (BufferedReader reader = Files.newBufferedReader(Paths.get("file.txt"))) {
// 使用reader
} catch (IOException e) {
e.printStackTrace();
}
不要忽視或吞掉異常。適當地處理IOException和其他可能的異常:
try {
Files.move(source, target);
} catch (IOException e) {
System.err.println("無法移動檔案: " + e.getMessage());
// 根據需要進行錯誤處理或重試
}
對於新的專案,優先考慮使用NIO.2(java.nio.file包)而不是舊的java.io.File類別。NIO.2提供更強大和一致的API。
記住不同的操作系統可能有不同的檔案系統行為。例如,檔案路徑分隔符、檔案名稱大小寫敏感性等:
String separator = File.separator; // 使用系統特定的路徑分隔符
在操作檔案之前,檢查檔案是否存在以及是否有適當的權限:
Path path = Paths.get("example.txt");
if (Files.exists(path) && Files.isReadable(path)) {
// 進行檔案操作
}
對於大檔案的讀寫操作,使用緩衝串流或NIO的Channel和Buffer來提高效能:
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("largefile.dat"))) {
// 使用緩衝串流讀取
}
在刪除檔案或目錄時要小心,特別是在遞迴刪除目錄時:
Files.walkFileTree(Paths.get("directory"), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
盡可能使用相對路徑而不是絕對路徑,這可以提高程式的可移植性:
Path relativePath = Paths.get("resources", "config.properties");
在多執行緒或多行程環境中,使用檔案鎖定來防止資料損壞:
try (FileChannel channel = FileChannel.open(path, StandardOpenOption.WRITE)) {
FileLock lock = channel.lock();
try {
// 執行需要鎖定的操作
} finally {
lock.release();
}
}
在進行關鍵的檔案操作之前,考慮備份重要資料:
Files.copy(source, backup, StandardCopyOption.REPLACE_EXISTING);
在讀寫文字檔案時,明確指定字元編碼以避免編碼問題:
Charset utf8 = StandardCharsets.UTF_8;
List<String> lines = Files.readAllLines(path, utf8);
本篇文章同步刊載: JYI.TW
筆者個人的網站: JUNYI